package sg.edu.nus.comp.cs4218.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import sg.edu.nus.comp.cs4218.extended1.IGrepTool;

public class GrepTool extends ATool implements IGrepTool {
	private String message;	
	private final String helpText = "The grep command searches one or more input files " + "\n"
								+ "for lines containing a match to a specified pattern. " + "\n"
								+ "The grep tool must work on all characters in UTF-8 encoding. " + "\n"
								+ "\n"
								+ "Command Format - grep [OPTIONS] PATTERN [FILE]" + "\n"
								+ "PATTERN - This specifies a regular expression pattern that describes a set of strings" + "\n"
								+ "FILE - Name of the file, when no file is present (denoted by \"-\") use standard input" + "\n"
								+ "OPTIONS" + "\n"
								+ "  -A NUM : Print NUM lines of trailing context after matching lines" + "\n"
								+ "  -B NUM : Print NUM lines of leading context before matching lines" + "\n"
								+ "  -C NUM : Print NUM lines of output context" + "\n"
								+ "  -c : Suppress normal output. Instead print a count of matching lines for each input file" + "\n"
								+ "  -o : Show only the part of a matching line that matches PATTERN" + "\n"
								+ "  -v : Select non-matching (instead of matching) lines" + "\n"
								+ "  -help : Brief information about supported options";
	
	private List<Integer> matches;
	private List<String> fileContent;
	/**
	 * Constructor
	 * 
	 * @param arguments
	 *            - Arguments the tool is going to be executed with.
	 */
	public GrepTool(String[] arguments) {
		super(arguments);
		setStatusCode(0);
		matches = new ArrayList<Integer>();
	}
	
	/**
	 * Searches for the number of lines of an input that match a regex expression
	 * 
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Number of line where the expression is matched
	 */
	@Override
	public int getCountOfMatchingLines(String pattern, String input) {
		if (input != null) {
			if (input.equals("") == false)
			{
				getOnlyMatchingLines(pattern, input);
			}
		}
		else
		{
			setStatusCode(1);
			message = "Invalid grep command";
		}
		return matches.size();
	}

	/**
	 * Searches the content of an input for lines that matches a regex expression
	 * 
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Matching line where the expression is matched
	 */
	@Override
	public String getOnlyMatchingLines(String pattern, String input) {
		String result = null;
		
		if (input != null) {
			fileContent = Arrays.asList(input.split("\\n"));
		}
		else {
			setStatusCode(1);
			message = "Invalid grep command";
			result = "Invalid grep command";
		}
		
		Pattern p = null;
		
		if (pattern != null) {
			p = Pattern.compile(pattern);
		}
		else {
			setStatusCode(1);
			message = "Invalid grep command";
			result = "Invalid grep command";
		}
		
		if (getStatusCode() == 0) {
			for (int i = 0; i < fileContent.size(); i++) {
				Matcher matcher = p.matcher(fileContent.get(i));
				if (matcher.find() == true) {
					matches.add(i);
				}
			}


			for (int line : matches) {
				if (result == null) {
					result = fileContent.get(line).trim();
				} else {
					result = result + "\n" + fileContent.get(line).trim();
				}
			}

			if (result == null) {
				result = "";
			}
			if (input.equals("") == true) {
				result = "";
			}
		}
		
		return result;
	}

	/**
	 * Searches the content of an input for lines that matches a regex expression 
	 * and returns that line plus the number of lines specified after it
	 * 
	 * @param option_A
	 *            - Positive trailing context number (non negative number)
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Matching line where the expression is matched plus the number of 
	 * lines specified after it
	 */
	@Override
	public String getMatchingLinesWithTrailingContext(int option_A, String pattern, String input) {
		getOnlyMatchingLines(pattern, input);
		
		if (getStatusCode() == 0 && option_A >= 0) {
			String result = null;
			for (int line : matches) {
				int index = line;
				int end = index + option_A;

				if (end > fileContent.size() - 1 ) {
					end = fileContent.size() - 1;
				}

				for (int i = index; i <= end ; i++) {
					if (result == null) {
						result = fileContent.get(i).trim();
					} else {
						result = result + "\n" + fileContent.get(i).trim();
					}
				}
				//result = result + "--" + "\n";
			}

			if (result == null) {
				result = "";
			}
			return result;		
		}
		else if (getStatusCode() == 0 && option_A < 0) {
			setStatusCode(1);
			message = "Invalid grep command";
			return message;
		}
		else {
			return message; //error code and message already set and checked in getOnlyMatchingLines method 
		}
	}

	/**
	 * Searches the content of an input for lines that matches a regex expression 
	 * and returns that line plus the number of lines specified before it
	 * 
	 * @param option_B
	 *            - Positive leading context number (non negative number)
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Matching line where the expression is matched plus the number of 
	 * lines specified before it
	 */
	@Override
	public String getMatchingLinesWithLeadingContext(int option_B, String pattern, String input) {
		getOnlyMatchingLines(pattern, input);
		
		if (getStatusCode() == 0 && option_B >= 0) {
			String result = null;
			for (int line : matches) {
				int index = line;
				int start = index - option_B;

				if (start < 0 ) {
					start = 0;
				}

				for (int i = start; i <= index ; i++) {
					if (result == null) {
						result = fileContent.get(i).trim();
					} else {
						result = result + "\n" + fileContent.get(i).trim();
					}
				}
				//result = result + "--" + "\n";
			}

			if (result == null) {
				result = "";
			}
			return result;
		}
		else if (getStatusCode() == 0 && option_B < 0) {
			setStatusCode(1);
			message = "Invalid grep command";
			return message;
		}
		else {
			return message; //error code and message already set and checked in getOnlyMatchingLines method 
		}
	}

	/**
	 * Searches the content of an input for lines that matches a regex expression 
	 * and returns that line plus the number of lines specified before and after it
	 * 
	 * @param option_C
	 *            - Positive trailing and leading context number (non negative number)
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Matching line where the expression is matched plus the number of 
	 * lines specified before and after it
	 */
	@Override
	public String getMatchingLinesWithOutputContext(int option_C, String pattern, String input) {
		getOnlyMatchingLines(pattern, input);
		
		if (getStatusCode() == 0 && option_C >= 0) {
			String result = null;
			for (int line : matches) {
				int index = line;
				int start = index - option_C;
				int end = index + option_C;

				if (start < 0 ) {
					start = 0;
				}

				if (end > fileContent.size() - 1 ) {
					end = fileContent.size() - 1;
				}

				for (int i = start; i <= end ; i++) {
					if (result == null) {
						result = fileContent.get(i).trim();
					} else {
						result = result + "\n" + fileContent.get(i).trim();
					}
				}
				//result = result + "--" + "\n";
			}

			if (result == null) {
				result = "";
			}
			return result;
		}
		else if (getStatusCode() == 0 && option_C < 0) {
			setStatusCode(1);
			message = "Invalid grep command";
			return message;
		}
		else {
			return message; //error code and message already set and checked in getOnlyMatchingLines method 
		}
	}

	/**
	 * Searches the content of an input for lines that matches a regex expression 
	 * and returns the parts that matches only
	 * 
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Parts where the expression is matched
	 */
	@Override
	public String getMatchingLinesOnlyMatchingPart(String pattern, String input) {
		getOnlyMatchingLines(pattern, input);		
		
		if (getStatusCode() == 0) {
			Pattern p = Pattern.compile(pattern);

			String result = null;
			for (int line : matches)
			{
				Matcher matcher = p.matcher(fileContent.get(line));
				
				if (matcher.find() == true) {
					if (result == null) {
						result = matcher.group().trim();
					} else {
						result = result  + "\n" + matcher.group().trim();
					}
				}
			}

			if (result == null) {
				result = "";
			}
			return result;
		}
		else {
			return message; //error code and message already set and checked in getOnlyMatchingLines method 
		}
	}

	/**
	 * Searches the content of an input for lines that do not match a regex expression
	 * 
	 * @param pattern
	 *            - The regex expression to look for
	 * @param input
	 *            - The content to search within
	 * @return Line where the expression is not matched
	 */
	@Override
	public String getNonMatchingLines(String pattern, String input) {
		getOnlyMatchingLines(pattern, input);
		List<String> inverseMatches = new ArrayList<String>();
		
		if (getStatusCode() == 0) {
			for (int i = 0; i < fileContent.size(); i++) {
				if (matches.contains(i) == false) {
					inverseMatches.add(fileContent.get(i));
				}
			}

			String result = null;
			for (String line : inverseMatches) {
				if (result == null) {
					result = line.trim();
				} else {
					result = result + "\n" + line.trim();
				}
			}

			if (result == null) {
				result = "";
			}
			return result;
		}
		else {
			return message; //error code and message already set and checked in getOnlyMatchingLines method 
		}
	}

	/**
	 * Display help for grep command.
	 * 
	 * @return help text for grep command
	 */
	@Override
	public String getHelp() {
		return helpText;
	}

	/**
	 * Searches and matches the content of a file with against a regex expression
	 * 
	 * @param stdin
	 *            - Input on stdin. NOT THE ARGUMENTS! Can be null.
	 * @return Output on stdout
	 */
	@Override
	public String execute(File workingDir, String stdin) {
		matches = new ArrayList<Integer>();
		
		if (super.args == null) {
			setStatusCode(1);
			message = "Invalid grep command";
		}
		
		if (getStatusCode() == 0) {
			if (Arrays.asList(args).contains("-help")) {
				message = getHelp();
			}
			else if (super.args.length > 1) {
				String input = getFilesContent(workingDir, stdin);
				processCommand(input);
			}
			else
			{
				setStatusCode(1);
				message = "Invalid grep command";
			}
		}

		return message;
	}
   
  /**
	* Calls the right method based on the provided command
	*/
	private void processCommand(String input) {
		try {
			if (getStatusCode() == 0) {
				if (super.args[0].equals("-v")) { 
					message = getNonMatchingLines(super.args[1], input);
				}
				else if (super.args[0].equals("-c")) {
					message = Integer.toString(getCountOfMatchingLines(super.args[1], input));
				}
				else if (super.args[0].equals("-o")) {
					message = getMatchingLinesOnlyMatchingPart(super.args[1], input);
				}				
				else if (super.args[0].equals("-A")) {
					message = getMatchingLinesWithTrailingContext(Integer.parseInt(super.args[1]), super.args[2], input);
				}
				else if (super.args[0].equals("-B")) {
					message = getMatchingLinesWithLeadingContext(Integer.parseInt(super.args[1]), super.args[2], input);
				}
				else if (super.args[0].equals("-C")) {
					message = getMatchingLinesWithOutputContext(Integer.parseInt(super.args[1]), super.args[2], input);
				}
				else {
					message = getOnlyMatchingLines(super.args[0], input);
				}
			}
		}
		catch (NumberFormatException e) {
			message = "Invalid grep command";
			setStatusCode(1);
		}
	}
	
	// Helper Functions
	
   /**
	* Decides which argument onward to start looking for file names baed on the command option
  * or to take from stdin
	*
	* @param workingDir
	*            - file indicating the workingDir of the command
	* @param stdin
	*            - the content of stdin (null possible)
	* @return String containing the contents that tje grep command will be appliued on
	*/
	private String getFilesContent(File workingDir, String stdin) {
		
		int fileNamesStartingIndex;
		//decide where to start looking for filenames
		if (super.args[0].equals("-v") || super.args[0].equals("-c") || super.args[0].equals("-o")) { 
			fileNamesStartingIndex = 2;
		}
		else if (super.args[0].equals("-A") || super.args[0].equals("-B") || super.args[0].equals("-C")) {
			fileNamesStartingIndex = 3;
		}
		else {
			fileNamesStartingIndex = 1;
		}
		
		String content = null;
		
		//iterate through the filenames
		for (int i = fileNamesStartingIndex; i < super.args.length; i++) 
		{
			if (super.args[i].equals("-") == false) {
				if (content == null) {
					content = getFromFile(Shell.getFile(workingDir, super.args[i])) + System.lineSeparator();
				} else {
					content = content + getFromFile(Shell.getFile(workingDir, super.args[i]))  + System.lineSeparator();
				}
			}
			else {
				if (stdin != null) {
					if (content == null) {
						content = stdin + System.lineSeparator();
					} else {
						content = content + stdin  + System.lineSeparator();
					}
				}
				else {
					setStatusCode(1);
					message = "Invalid grep command";
				}
			}
		}
		return content;
	}
	
   /**
	* Provides error catching when attempting to read the file provided
	*
	* @param fileIn
	*            - The file to open and read
	* @return String containing the contents of the file
	*/
	private String getFromFile (File fileIn) {
		
		String content = null;
		try {
			content = readFromFile(fileIn);
		} catch (Exception e) {
			setStatusCode(1);
			message = "Unable to read file";
		}

		return content;
	}
	
   /**
	* Actual opening and reading of the file provided
	*
	* @param fileIn
	*            - The file to open and read
	* @return String containing the contents of the file
	*/
	private String readFromFile(File fileIn) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(
				new FileInputStream(fileIn), "UTF8"));
		StringBuilder fileContent = new StringBuilder();
		String line = in.readLine();

		while (line != null) {
			fileContent.append(line + "\n");
			line = in.readLine();
		}
		in.close();

		return fileContent.toString();
	}
	

}


